﻿using System;
using System.Reflection;
using System.Reflection.Emit;
using Sigil.Impl;

namespace Sigil
{
	public partial class Emit<DelegateType>
	{
		/// <summary>
		/// Push a 1 onto the stack if b is true, and 0 if false.
		///
		/// Pushed values are int32s.
		/// </summary>
		public Emit<DelegateType> LoadConstant(Boolean b)
		{
			return LoadConstant(b ? 1 : 0);
		}

		/// <summary>
		/// Push a constant int32 onto the stack.
		/// </summary>
		public Emit<DelegateType> LoadConstant(Int32 i)
		{
			switch (i)
			{
				case -1: UpdateState(OpCodes.Ldc_I4_M1, Wrap(StackTransition.Push<Int32>(), "LoadConstant")); return this;
				case 0: UpdateState(OpCodes.Ldc_I4_0, Wrap(StackTransition.Push<Int32>(), "LoadConstant")); return this;
				case 1: UpdateState(OpCodes.Ldc_I4_1, Wrap(StackTransition.Push<Int32>(), "LoadConstant")); return this;
				case 2: UpdateState(OpCodes.Ldc_I4_2, Wrap(StackTransition.Push<Int32>(), "LoadConstant")); return this;
				case 3: UpdateState(OpCodes.Ldc_I4_3, Wrap(StackTransition.Push<Int32>(), "LoadConstant")); return this;
				case 4: UpdateState(OpCodes.Ldc_I4_4, Wrap(StackTransition.Push<Int32>(), "LoadConstant")); return this;
				case 5: UpdateState(OpCodes.Ldc_I4_5, Wrap(StackTransition.Push<Int32>(), "LoadConstant")); return this;
				case 6: UpdateState(OpCodes.Ldc_I4_6, Wrap(StackTransition.Push<Int32>(), "LoadConstant")); return this;
				case 7: UpdateState(OpCodes.Ldc_I4_7, Wrap(StackTransition.Push<Int32>(), "LoadConstant")); return this;
				case 8: UpdateState(OpCodes.Ldc_I4_8, Wrap(StackTransition.Push<Int32>(), "LoadConstant")); return this;
			}

			if (i >= sbyte.MinValue && i <= sbyte.MaxValue)
			{
				Byte asByte;
				unchecked
				{
					asByte = (Byte)i;
				}

				UpdateState(OpCodes.Ldc_I4_S, asByte, Wrap(StackTransition.Push<Int32>(), "LoadConstant"));
				return this;
			}

			UpdateState(OpCodes.Ldc_I4, i, Wrap(StackTransition.Push<Int32>(), "LoadConstant"));

			return this;
		}

		/// <summary>
		/// Push a constant int32 onto the stack.
		/// </summary>
		public Emit<DelegateType> LoadConstant(UInt32 i)
		{
			switch (i)
			{
				case UInt32.MaxValue: UpdateState(OpCodes.Ldc_I4_M1, Wrap(StackTransition.Push<Int32>(), "LoadConstant")); return this;
				case 0: UpdateState(OpCodes.Ldc_I4_0, Wrap(StackTransition.Push<Int32>(), "LoadConstant")); return this;
				case 1: UpdateState(OpCodes.Ldc_I4_1, Wrap(StackTransition.Push<Int32>(), "LoadConstant")); return this;
				case 2: UpdateState(OpCodes.Ldc_I4_2, Wrap(StackTransition.Push<Int32>(), "LoadConstant")); return this;
				case 3: UpdateState(OpCodes.Ldc_I4_3, Wrap(StackTransition.Push<Int32>(), "LoadConstant")); return this;
				case 4: UpdateState(OpCodes.Ldc_I4_4, Wrap(StackTransition.Push<Int32>(), "LoadConstant")); return this;
				case 5: UpdateState(OpCodes.Ldc_I4_5, Wrap(StackTransition.Push<Int32>(), "LoadConstant")); return this;
				case 6: UpdateState(OpCodes.Ldc_I4_6, Wrap(StackTransition.Push<Int32>(), "LoadConstant")); return this;
				case 7: UpdateState(OpCodes.Ldc_I4_7, Wrap(StackTransition.Push<Int32>(), "LoadConstant")); return this;
				case 8: UpdateState(OpCodes.Ldc_I4_8, Wrap(StackTransition.Push<Int32>(), "LoadConstant")); return this;
			}

			if (i <= sbyte.MaxValue)
			{
				Byte asByte;
				unchecked
				{
					asByte = (Byte)i;
				}

				UpdateState(OpCodes.Ldc_I4_S, asByte, Wrap(StackTransition.Push<Int32>(), "LoadConstant"));
				return this;
			}

			UpdateState(OpCodes.Ldc_I4, i, Wrap(StackTransition.Push<Int32>(), "LoadConstant"));

			return this;
		}

		/// <summary>
		/// Push a constant int64 onto the stack.
		/// </summary>
		public Emit<DelegateType> LoadConstant(Int64 l)
		{
			UpdateState(OpCodes.Ldc_I8, l, Wrap(StackTransition.Push<Int64>(), "LoadConstant"));

			return this;
		}

		/// <summary>
		/// Push a constant int64 onto the stack.
		/// </summary>
		public Emit<DelegateType> LoadConstant(UInt64 l)
		{
			UpdateState(OpCodes.Ldc_I8, l, Wrap(StackTransition.Push<Int64>(), "LoadConstant"));

			return this;
		}

		/// <summary>
		/// Push a constant Single onto the stack.
		/// </summary>
		public Emit<DelegateType> LoadConstant(Single f)
		{
			UpdateState(OpCodes.Ldc_R4, f, Wrap(StackTransition.Push<Single>(), "LoadConstant"));

			return this;
		}

		/// <summary>
		/// Push a constant Double onto the stack.
		/// </summary>
		public Emit<DelegateType> LoadConstant(Double d)
		{
			UpdateState(OpCodes.Ldc_R8, d, Wrap(StackTransition.Push<Double>(), "LoadConstant"));

			return this;
		}

		/// <summary>
		/// Push a constant String onto the stack.
		/// </summary>
		public Emit<DelegateType> LoadConstant(String str)
		{
			UpdateState(OpCodes.Ldstr, str, Wrap(StackTransition.Push<String>(), "LoadConstant"));

			return this;
		}

		/// <summary>
		/// Push a constant RuntimeFieldHandle onto the stack.
		/// </summary>
		public Emit<DelegateType> LoadConstant(FieldInfo field)
		{
			if (field == null)
			{
				throw new ArgumentNullException("field");
			}

			UpdateState(OpCodes.Ldtoken, field, Wrap(StackTransition.Push<RuntimeFieldHandle>(), "LoadConstant"));

			return this;
		}

		/// <summary>
		/// Push a constant RuntimeMethodHandle onto the stack.
		/// </summary>
		public Emit<DelegateType> LoadConstant(MethodInfo method)
		{
			if (method == null)
			{
				throw new ArgumentNullException("method");
			}

			UpdateState(OpCodes.Ldtoken, method, Type.EmptyTypes, Wrap(StackTransition.Push<RuntimeMethodHandle>(), "LoadConstant"));

			return this;
		}

		/// <summary>
		/// Push a constant RuntimeTypeHandle onto the stack.
		/// </summary>
		public Emit<DelegateType> LoadConstant<Type>()
		{
			return LoadConstant(typeof(Type));
		}

		/// <summary>
		/// Push a constant RuntimeTypeHandle onto the stack.
		/// </summary>
		public Emit<DelegateType> LoadConstant(Type type)
		{
			if (type == null)
			{
				throw new ArgumentNullException("type");
			}

			UpdateState(OpCodes.Ldtoken, type, Wrap(StackTransition.Push<RuntimeTypeHandle>(), "LoadConstant"));

			return this;
		}

		/// <summary>
		/// Loads a null reference onto the stack.
		/// </summary>
		public Emit<DelegateType> LoadNull()
		{
			UpdateState(OpCodes.Ldnull, Wrap(StackTransition.Push<NullType>(), "LoadNull"));

			return this;
		}
	}
}